package net.daum.mail;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Properties;

import javax.mail.Flags;
import javax.mail.Flags.Flag;
import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.event.ConnectionEvent;
import javax.mail.event.ConnectionListener;
import javax.mail.search.FlagTerm;
import javax.mail.search.FromStringTerm;
import javax.mail.search.OrTerm;
import javax.mail.search.SearchTerm;
import javax.mail.search.SubjectTerm;

import org.apache.commons.lang.StringUtils;

import com.sun.mail.imap.IMAPFolder;
import com.sun.mail.imap.IMAPMessage;
import com.sun.mail.imap.IMAPStore;

public class IMAPClient implements ConnectionListener {
	protected static final String DEFAULT_TIMEOUT = 55 * 1000 + "";
	protected static final String DEFAULT_SOCKET_TIMEOUT = 55 * 1000 + "";
	protected static final String FETCH_SIZE = 1024 * 128 + "";
	protected static final String APPEND_BUFFER_SIZE = 1024 * 512 + "";
	protected static final String PROTOCOL_IMAPS = "imaps";
	protected static final String PROTOCOL_IMAP = "imap";

	protected IMAPStore store = null;
	protected String host = null;
	protected String port = null;
	protected String username = null;
	protected String password = null;
	protected String security = null;
	protected Properties props = null;
	protected Session session = null;
	
	/**
	 * 
	 * @param host
	 * @param username
	 * @param password
	 * @param port
	 * @param security
	 * @throws MessagingException
	 */
	public IMAPClient(String host, String username, String password, String port, String security) throws MessagingException {
		this.host = host;
		this.username = username;
		this.password = password;
		this.port = port;
		this.security = security;
	}

	protected IMAPStore createStore(boolean debug) throws MessagingException {
		boolean isSsl = isSsl();
		String protocol = isSsl ? PROTOCOL_IMAPS : PROTOCOL_IMAP;
		session = createSession(protocol, isSsl, debug);
		session.setDebug(debug);
		return (IMAPStore) session.getStore(protocol);
	}

	protected Session createSession(String protocol, boolean isSsl, boolean debug) {
		Properties props = getProperties();
		if (!StringUtils.isEmpty(port)) {
			props.setProperty("mail." + protocol + ".port", port);
			props.setProperty("mail." + protocol + ".socketFactory.port", port);
		}

		props.setProperty("mail." + protocol + ".connectiontimeout", DEFAULT_TIMEOUT);
		props.setProperty("mail." + protocol + ".timeout", DEFAULT_SOCKET_TIMEOUT);
		props.setProperty("mail." + protocol + ".connectionpoolsize", "3");
		props.setProperty("mail." + protocol + ".connectionpool.debug", "false");
		props.setProperty("mail." + protocol + ".fetchsize", FETCH_SIZE);
		props.setProperty("mail." + protocol + ".appendbuffersize", APPEND_BUFFER_SIZE);
		props.setProperty("mail." + protocol + ".ssl.trust", "*");
		props.setProperty("mail.store.protocol", protocol);
		props.setProperty("mail.debug.auth", String.valueOf(debug));
		
		Properties systemProps = System.getProperties();
		systemProps.setProperty("mail.mime.ignoreunknownencoding", "true");
		systemProps.setProperty("mail.mime.base64.ignoreerrors", "true");

		return Session.getInstance(props, null);
	}

	/**
	 * @return props
	 */
	public Properties getProperties() {
		if (props == null) {
			props = new Properties();
		}
		return props;
	}
	
	/**
	 * set Properties
	 * @param props
	 */
	public void setProperties(Properties props) {
		this.props = props;
	}
	
	/**
	 * Make connection
	 * @return IMAPStore
	 * @throws MessagingException
	 */
	public IMAPStore connect() throws MessagingException {
		return connect(false);
	}
	
	/**
	 * Make connection
	 * @param debug
	 * @return IMAPStore
	 * @throws MessagingException
	 */
	public IMAPStore connect(boolean debug) throws MessagingException {

		if (store == null) {
			store = createStore(debug);
			store.addConnectionListener(this);
		}

		store.connect(host, username, password);

		return store;
	}

	private boolean isSsl() {
		return StringUtils.equals(security, "ssl") || StringUtils.equals(security, "tls");
	}

	/**
	 * Release connection
	 */
	public void release() {
		try {
			if (null != store) {
				store.close();
			}
		} catch (MessagingException e) {
			// ignore
		} finally {
			store = null;
		}
	}
	
	public IMAPFolder[] getFolders() throws MessagingException {
		return (IMAPFolder[]) getStore().getDefaultFolder().list("*");
	}

	public IMAPFolder getFolder(String folderName) throws MessagingException {
		IMAPFolder folder = (IMAPFolder) getStore().getFolder(folderName);
		return folder;
	}

	public void createFolder(String folderName) throws MessagingException {
		Folder folder = getStore().getFolder(folderName);
		folder.create(Folder.HOLDS_MESSAGES);
	}

	public void removeFolder(String folderName) throws MessagingException {
		Folder folder = getStore().getFolder(folderName);
		folder.delete(true);
	}

	public Message getMessage(Folder folder, int index) throws MessagingException {
		return folder.getMessage(index);
	}

	public Message[] addMessages(IMAPFolder folder, Message[] messages) throws MessagingException {
		return folder.addMessages(messages);
	}

	public void removeMessage(IMAPFolder folder, long uid) throws MessagingException {
		Message message = folder.getMessageByUID(uid);
		message.setFlag(Flag.DELETED, true);
		folder.expunge(new Message[] { message });
	}

	public void removeMessages(IMAPFolder folder, Message[] messages) throws MessagingException {
		folder.setFlags(messages, new Flags(Flag.DELETED), true);
		folder.expunge(messages);
	}

	public void removeMessages(IMAPFolder folder, long maxUid) throws MessagingException {
		if (maxUid < 1) {
			return;
		}
		Message[] messages = folder.getMessagesByUID(1, maxUid);
		if (messages == null || messages.length < 1) {
			return;
		}
		folder.setFlags(1, messages[messages.length - 1].getMessageNumber(), new Flags(Flag.DELETED), true);
		folder.expunge();
	}

	public void copyMessages(IMAPFolder srcFolder, IMAPFolder dstFolder, long uid) throws MessagingException {
		srcFolder.copyMessage(uid, dstFolder);
	}
	
	public void copyMessages(IMAPFolder srcFolder, IMAPFolder dstFolder, long uids[]) throws MessagingException {
		srcFolder.copyMessages(uids, dstFolder);
	}
	
	public void copyMessages(IMAPFolder srcFolder, IMAPFolder dstFolder, Message[] messages) throws MessagingException {
		srcFolder.copyMessages(messages, dstFolder);
	}

	
	public void moveMessages(IMAPFolder srcFolder, IMAPFolder dstFolder, Message[] messages) throws MessagingException {
		srcFolder.moveMessages(messages, dstFolder);
	}

	public void moveMessages(IMAPFolder srcFolder, IMAPFolder dstFolder, long maxUid) throws MessagingException {
		Message[] messages = srcFolder.getMessagesByUID(1, maxUid);
		if (messages == null || messages.length < 1) {
			return;
		}
		moveMessages(srcFolder, dstFolder, messages);
	}

	public void seenMessage(IMAPFolder folder, long uid) throws MessagingException {
		setFlags(folder, uid, new Flags(Flag.SEEN));
	}

	public void seenMessages(IMAPFolder folder, long[] uids) throws MessagingException {
		setFlags(folder, uids, new Flags(Flag.SEEN));
	}

	public void unseenMessage(IMAPFolder folder, long uid) throws MessagingException {
		unsetFlags(folder, uid, new Flags(Flag.SEEN));
	}

	public void unseenMessages(IMAPFolder folder, long[] uids) throws MessagingException {
		unsetFlags(folder, uids, new Flags(Flag.SEEN));
	}

	protected void setFlags(IMAPFolder folder, long uid, Flags flags) throws MessagingException {
		Message message = folder.getMessageByUID(uid);
		message.setFlags(flags, true);
	}
	
	protected void setFlags(IMAPFolder folder, long[] uids, Flags flags) throws MessagingException {
		Message[] msgs = folder.getMessagesByUID(uids);
		folder.setFlags(msgs, flags, true);
	}

	protected void unsetFlags(IMAPFolder folder, long uid, Flags flags) throws MessagingException {
		Message message = folder.getMessageByUID(uid);
		message.setFlags(flags, false);
	}
	
	protected void unsetFlags(IMAPFolder folder, long[] uids, Flags flags) throws MessagingException {
		Message[] msgs = folder.getMessagesByUID(uids);
		folder.setFlags(msgs, flags, false);
	}

	public void flagMessage(IMAPFolder folder, long uid, String flagType) throws MessagingException {
		Flags flags = new Flags();
		flags.add(Flag.FLAGGED);
		flags.add(flagType);
		setFlags(folder, uid, flags);
	}

	public void flagMessage(IMAPFolder folder, long uid, Flags flags) throws MessagingException {
		setFlags(folder, uid, flags);
	}

	public void flagMessages(IMAPFolder folder, long[] uids, Flags flags) throws MessagingException {
		setFlags(folder, uids, flags);
	}

	public void unflagMessage(IMAPFolder folder, long uid) throws MessagingException {
		unsetFlags(folder, uid, new Flags(Flag.FLAGGED));
	}

	public void unflagMessages(IMAPFolder folder, long[] uids) throws MessagingException {
		unsetFlags(folder, uids, new Flags(Flag.FLAGGED));
	}

	public void answeredMessage(IMAPFolder folder, long uid) throws MessagingException {
		setFlags(folder, uid, new Flags(Flag.ANSWERED));
	}

	public void unansweredMessage(IMAPFolder folder, long uid) throws MessagingException {
		unsetFlags(folder, uid, new Flags(Flag.ANSWERED));
	}

	public void draftMessage(IMAPFolder folder, long uid) throws MessagingException {
		setFlags(folder, uid, new Flags(Flag.DRAFT));
	}

	public IMAPMessage[] searchMessage(IMAPFolder folder, String item) throws MessagingException {
		SubjectTerm subjectTerm = new SubjectTerm(item);
		FromStringTerm fromTerm = new FromStringTerm(item);
		SearchTerm term = new OrTerm(new SearchTerm[] { subjectTerm, fromTerm });
		Message[] messages = folder.search(term);
		return (IMAPMessage[]) messages;
	}

	public ArrayList<Message> getUnseenMessages() throws MessagingException {
		ArrayList<Message> messages = new ArrayList<Message>();
		for (IMAPFolder folder : getFolders()) {
			folder.open(Folder.READ_ONLY);
			FlagTerm flagTerm = new FlagTerm(new Flags(Flags.Flag.SEEN), false);
			messages.addAll(Arrays.asList(folder.search(flagTerm)));
			folder.close(false);
		}

		return messages;
	}

	public Message[] getMessages(IMAPFolder folder, int start, int end) throws MessagingException {
		if (start < 0) {
			start = 1;
		}
		Message[] messages = folder.getMessages(start, end);
		return messages;
	}

	public Message[] getMessagesByUID(IMAPFolder folder, long start, long end) throws MessagingException {
		Message[] messages = folder.getMessagesByUID(start, end);
		return messages;
	}

	public Message[] getMessagesByUID(IMAPFolder folder, long[] uids) throws MessagingException {
		Message[] messages = folder.getMessagesByUID(uids);
		return messages;
	}

	public Message getMessageByUID(IMAPFolder folder, long uid) throws MessagingException {
		Message message = folder.getMessageByUID(uid);
		return message;
	}

	public IMAPStore getStore() throws MessagingException {
		if (!isConnected()) {
			connect();
		}

		return store;
	}
	
	public Session getSession() {
		return session;
	}
	
	public boolean isConnected() {
		return store != null && store.isConnected();
	}

	
	@Override
	public void closed(ConnectionEvent event) {
		store.removeConnectionListener(this);
		release();
	}

	@Override
	public void opened(ConnectionEvent event) {
	}

	@Override
	public void disconnected(ConnectionEvent event) {
	}

	public boolean hasCapability(String capability) throws MessagingException {
		return getStore().hasCapability(capability);
	}
}
